package weeny.pre.display;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.data.Graph;
import prefuse.data.tuple.TupleSet;
import prefuse.visual.VisualTupleSet;
import weeny.pre.event.*;
import weeny.pre.util.Marker;


/**
 * Add group change listener, so when a group is added or removed,
 * the listeners will be notified.
 * 
 * Add markers holder, so we can add Marker by just calling addMarkers(...).
 * There are also some helper functions.
 * 
 * Add Display change listener (To do so, we need to change the visibility of
 * {@link Visualization#addDisplay} and {@link Visualization#removeDisplay} to
 * protected! instead of package), so when a display is added or removed, the
 * listeners will be notified. 
 * 
 * 
 * @author weeny
 */
public class MoreVisualization extends Visualization{
	protected static final String[] ZERO_STRS = new String[0];
	protected Set<String> allGroups = new TreeSet<String>();
	public Set<String> getGroups(){
		return Collections.unmodifiableSet(allGroups);
	}
	protected Map<String, Marker> markerMap = new HashMap<String, Marker>();
	public Marker addMarker(String group, Graph graph){
		if (markerMap.containsKey(group)) {
			throw new IllegalArgumentException("Already has marker: "+group);
		}
		Marker marker = new Marker(this, group, graph);
		markerMap.put(group, marker);
		return marker;
	}
	public Marker addMarker(String group){
		if (markerMap.containsKey(group)) {
			throw new IllegalArgumentException("Already has marker: "+group);
		}
		Marker marker = new Marker(this, group);
		markerMap.put(group, marker);
		return marker;
	}
	public Marker getMarker(String group){
		return markerMap.get(group);
	}
	public MoreVisualization() {
		//init what we need
		Initialization.init();
		//because super construction adds these two focus groups by calling
		//addFocusGroup, but we override this function can use groups which
		//is null at this time point because of the super/sub class thing,
		//so we need to check groups at that function and manually add these
		//two focus groups here and manually notify all listeners.
		//this is ugly, but works fine for now.
		allGroups.add(FOCUS_ITEMS);
		allGroups.add(SELECTED_ITEMS);
		fireGroupUpdate(new String[]{FOCUS_ITEMS, SELECTED_ITEMS}, ZERO_STRS);
	}
	
	//-------------------------------------------------------------------------
	//Functions changed for group update listener
	@Override
	protected void addDataGroup(String group, VisualTupleSet ts, TupleSet src){
		super.addDataGroup(group, ts, src);
		if (allGroups == null)
			return;
		allGroups.add(group);
		fireGroupUpdate(new String[]{group}, ZERO_STRS);
	}
	@Override
	public void addFocusGroup(String group) {
		super.addFocusGroup(group);
		if (allGroups == null) 
			return;
		allGroups.add(group);
		fireGroupUpdate(new String[]{group}, ZERO_STRS);
	}
	@Override
	public void addFocusGroup(String group, TupleSet ts) {
		super.addFocusGroup(group);
		if (allGroups == null)
			return;
		allGroups.add(group);
		fireGroupUpdate(new String[]{group}, ZERO_STRS);
	}
	@Override
	public synchronized boolean removeGroup(String group) {
		Set<String> removed = new HashSet<String>();
		boolean result = super.removeGroup(group);
		for (String string : allGroups){
			if (getGroup(string) == null) {
				removed.add(string);
			}
		}
		allGroups.removeAll(removed);
		fireGroupUpdate(ZERO_STRS, removed.toArray(ZERO_STRS));
		return result;
	}
	
	//-------------------------------------------------------------------------
	//Functions changed for display listener
	@Override
	protected void addDisplay(Display display) {
		super.addDisplay(display);
		for (DisplayListener listener : displayListeners) {
			listener.hasAddedDisplay(display);
		}
	}
	@Override
	protected boolean removeDisplay(Display display) {
		for (DisplayListener listener : displayListeners) {
			listener.willRemoveDisplay(display);
		}
		return super.removeDisplay(display);
	}
	
	//-------------------------------------------------------------------------
	//display listener
	protected List<DisplayListener> displayListeners = new ArrayList();
	public void addDisplayListener(DisplayListener lsn){
		displayListeners.add(lsn);
	}
	public DisplayListener removeDisplayListener(DisplayListener lsn){
		boolean flag = displayListeners.remove(lsn);
		return flag ? lsn : null;
	}
	
	//-------------------------------------------------------------------------
	//group listener
	protected List<GroupListener> groupListener = new ArrayList();
	/**
	 * Add/Remove a group listener to this visualization. The group listener is
	 * called when a new group name is added/removed in this visualization. For
	 * example, {@link #addDataGroup(String, VisualTupleSet, TupleSet)} and
	 * {@link #addFocusGroup(String)}, etc. No matter the group name is a focus
	 * group name or data group name. Please notice, sometimes you may think you
	 * only add ONE group name, but actually there're several group names added.
	 * For example, you call {@link #addGraph(String, prefuse.data.Graph)}. The
	 * There are three groups names added: graphName.nodes, graphName.edges, and
	 * graphName!
	 * 
	 * @param lsn
	 */
	public void addGroupListener(GroupListener lsn){
		groupListener.add(lsn);
	}
	/**
	 * @see #addGroupListener(GroupListener)
	 */
	public GroupListener removeGroupListener(GroupListener lsn){
		boolean flag = groupListener.remove(lsn);
		return flag ? lsn : null;
	}
	protected void fireGroupUpdate(String[] added, String[] removed){
		for (GroupListener lsn : groupListener){
			lsn.groupUpdated(added, removed);
		}
	}
}